Copyright 2010-2012, John R. Ellis. You may use this toolkit for any purpose, as long as you include this notice in any versions derived in whole or part from these files.
This Debugging Toolkit consists of:
- strict.lua: The standard script from the Lua distribution that detects undeclared global variables (usually typos).
- Require.lua: A replacement for the standard require that provides the ability to reload all files and to define a search path for loading .lua files from shared directories.
- debugscript.lrdevplugin: Provides quick, easy loading and reloading of scripts (whether or not they're part of a plugin), error trapping, and automatic display of the offending source line in your favorite text editor.
- Debug.lua: A standalone debugging module that provides an interactive debugger with breakpoints, stack traces, and evaluation of expressions; a "pretty printer" that nicely formats any Lua value (including nested and circular tables); some logging tools; and a rudimentary elapsed-time profiler for functions.
Each of these components can be used standalone, without the others.
Debugging Export- and Publish-Service Plugins
Evaluating Expressions and Statements
1. Unzip the toolkit and place debugscript.lrdevplugin in your plugins folder.
2. Use the Lightroom Plug-in Manager to add debugscript.lrdevplugin as a plugin.
3. Copy Require.lua from the debugscript.lrdevplugin directory to the directory of the plugin you want to debug.
4. Place these lines at the top of your menu plugin's main file or at the top of the export- or publish-service definition script:
local Require = require 'Require'.path ("../debugscript.lrdevplugin")
local Debug = require 'Debug'.init ()
require 'strict'
5. Wrap all LrView callback functions, LrTasks main functions, and your main showDialog() function with Debug.showErrors, as described below (see below for details).
6. Set a breakpoint by inserting in your code a call to Debug.pause:
Debug.pause (x, i, items [i].prev)
7. Run File > Plug-in Extras > Debug Script.
8. Browse to the main file of your plugin and click Run. Your plugin will pause at the breakpoint and invoke the debugger.
Steps 1–4 from above.
5. Wrap all LrView callback functions and LrTasks main functions as described below (see below for details).
Make sure your service definition script refers to functions exported by a global namespace defined in another file, e.g. MyService.lua, and wrap those functions with Debug.showErrors:
return {
startDialog = Debug.showErrors (MyService.startDialog),
endDialog = Debug.showErrors (MyService.endDialog),
exportPresetFields = { { key = ...}
6. Copy DebugScript.lua from the debugscript.lrdevplugin directory to the directory of the plugin you want to debug.
7. Add an entry to your plugin's Info.lua to run DebugScript.lua as a File menu command:
LrExportMenuItems = {
{title = "Debug Script", file = "DebugScript.lua"}}
8. Set a breakpoint by inserting in your code a call to Debug.pause:
Debug.pause (x, i, items [i].prev)
9. Run File > Plug-in Extras > your plugin > Debug Script. Make sure you run the version of Debug Script from your plugin (the plugin being debugged), not from any other plugin – this ensures that it loads files into the correct plugin’s environment.
10. Browse to MyService.lua, click Run to load MyService.lua, then click Close to exit Debug Script.
11. Run the publish or export service. Your plugin will pause at the breakpoint and invoke the debugger.
12. If you edit MyService.lua or modules require’d by MyService.lua, rerun Debug Script and click Run to reload the files and catch any errors in your text editor.
Perhaps the most common Lua programming mistake is to mistype the name of variable—Lua will silently assume the mistyped name is a global variable. If you include the following at the top of each file:
require 'strict.lua'
any attempt to access a global variable that hasn't been "declared" will raise an error. You "declare" a global variable simply by assigning it a value (or defining it as a function) at the top level of a file.
Of everything in this toolkit, strict.lua has the biggest bang for the buck. Use it.
The Debug Script plugin provides quick, easy loading and reloading of scripts (whether or not they're part of a plugin), error trapping, and automatic display of the offending source line in your favorite text editor. You can use Debug Script to run code without using the Plug-in Manager or creating a separate plugin directory and Info.lua. Once you've used Debug Script just a few times, you'll never want to reload via the Plug-in Manager again.
To use the Debug Script plugin:
1. File > Plug-in Extras > Debug Script.
2. Browse to the .lua file you want to run. This may be the main script of your plugin, or it could be any other .lua file.
3. Each time you click Run, Debug Script will reload the file and any files loaded by nested require’s, executing the files in a new global environment. If an error occurs, the source file will be displayed in the configured text editor at the appropriate line.
Click Debug options to configure the text editor you want to use with Debug Script. On Windows, by default it will use TextPad if it's installed, Notepad otherwise. On Mac, by default it will use TextEdit. (Neither Notepad nor TextEdit know how to display a source file at a particular line number.)
The Show new globals option shows global variables that were defined as a result of loading the file. If your programming style avoids global variables, this helps you enforce that convention.
The Reload all required scripts option forces the reloading of all nested require’d files. The only reason to uncheck this is to test out persistent state that may be maintained by some of the modules loaded by the main file.
The Clear LrPrefs.prefsForPlugin option deletes any plugin preferences before executing the script, letting you test how your plugin behaves right after it’s been installed.
Require.lua provides a compatible replacement for the standard require that provides the ability to for loading shared files from common directories and to automatically reload all files each time your plugin executes during development (but not when released).
First, place a copy of Require.lua in your plugin directory. To load files from a shared directory common that's a sibling of your plugin directory, put the following at the top of your plugin's main file:
local Require = require 'Require'.path ("../common")
Now require will look in the common directory for any files that aren't found in the plugin directory. You can provide more than one directory in the call to path.
To deploy your plugin to customers, you could continue to use the same directory structure. However, I recommend compiling all the plugin-specific and common files into a single release directory that gets shipped to your customers. That simplifies the installation for them by not having to create the common directory. And if you have two plugins sharing a common file, each gets its own copy (and version) of the file, letting a customer upgrade one plugin without upgrading the other. (Since each plugin executes in its own environment, each will compile and load its own copy of a shared file, regardless of whether it is loaded from a common directory.)
To force reloading of require’d files each time you run the plugin, without invoking the Plug-in Manager, put the following at the top of your plugin's main file:
local Require = require 'Require'.reload ()
Whenever the main file is executed from a directory ending in .lrdevplugin, any subsequent nested require’s will be reloaded, regardless if they had been previously loaded. The .reload() option has no effect when executed from a directory ending in .lrplugin (a release directory) – your released plugin will load files just once, when it is first invoked.
Note that if you use Debug Script, there is no need for using the .reload() option—Debug Script lets you control reloading.
You can combine both .path() and .reload():
local Require = require 'Require'.path ("../common").reload ()
Debug.lua provides a simple interactive debugger with breakpoints, stack traces, and evaluation of expressions. Though it has some significant limitations imposed by the SDK, I still find it invaluable for debugging.
1. Put the following in your main file:
local Debug = require 'Debug'.init ()
When your plugin executes from a directory ending in .lrdevplugin, the debugger will be enabled; when it executes from any other directory (e.g. a release directory ending in .lrplugin) it will be disabled and have no impact on execution.
Pass true to init to always enable the debugger, regardless of the containing directory:
local Debug = require 'Debug'.init (true)
2. Wrap each function that can be called directly from Lightroom with Debug.showErrors, ensuring that the debugger will be invoked when an error occurs. In particular, wrap LrView callback functions, the main functions of tasks, and all functions provided in an export- or publish-service definition script:
viewFactory:push_button {title = "Do it", action = Debug.showErrors (doItPushed)}
viewFactory:edit_field {validate = Debug.showErrors (myValidate)}
LrTask.startAsyncTask (Debug.showErrors (function () ...))
LrFunctionContext.postAsyncTaskWithContext (
Debug.showErrors (function (context) ...))
return {
startDialog = Debug.showErrors (MyService.startDialog),
endDialog = Debug.showErrors (MyService.endDialog),
...
3. Wrap the main function of your File or Library menu plugin (often called showDialog) with Debug.showErrors. For example, here's how to invoke the main showDialog function of a menu plugin:
LrFunctionContext.callWithContext ("showDialog",
Debug.showErrors (function (context)
showDialog (context)
end))
Once you’ve prepared your code, the debugger window will appear whenever a runtime error occurs, and the configured text editor will display the offending source line. In the debugger window, you'll see the error, the call stack, and the values of arguments (if at a function breakpoint).
In the debugger window, use the ^ and V buttons to select other frames in the call stack that have known source files. The Edit button will open the text editor again on the currently selected frame.
The SDK often limits the visibility of the call stack. In particular, LrTasks.pcall and other functions that call it, such as LrFunctionContext.callWithContext, hide the call stack prior to their call, when invoked from tasks other than the main task.
In Options, Debug lets you choose whether to trap errors at function breakpoints or see the entire call stack—in tasks other than the main task, you can't do both.
You can enter an expression and click Eval, and the result will be "pretty printed" in the results pane. Due to limitations imposed by the SDK, however, expressions can only refer to global names and function parameters. Because of this limitation, you may want to use global, rather than local, variables to hold your modules/namespaces and key data structures. Debug.pause lets you access selected local variables at a breakpoint.
Note that you can always re-import a Lightroom namespace to access it in the Eval field, e.g.
import 'LrApplication'.activeCatalog ():getPath ()
To execute a statement rather than evaluate an expression, prefix it with a period:
.x [i] = myFunc(j)
Go resumes execution.
Go until return resumes execution from a function breakpoint, ignoring nested function breakpoints until this call to the function returns.
Go until error resumes execution, ignoring existing function breakpoints and calls to Debug.pause, until the next error occurs.
Stop terminates execution by raising a distinguished error.
There are three ways of setting breakpoints:
1. Insert a call to Debug.pause in your code, optionally passing values you want to inspect in the debugger:
Debug.pause (items, testItem (items [1]), x)
The values will appear in the Arguments pane of the debugger, and you can refer to them in Eval expressions.
Debug.pauseIf invokes the debugger only if its first argument is true:
Debug.pauseIf (i > n, i, n, items)
2. In the debugger window, the Function breaks command lets you enter the name of a globally accessible function and an optional conditional expression. The function name must be a global variable (myFunc) or a name in a global namespace (MyModule.myFunc). Insert a call to Debug.pause at the beginning of your plugin so you can set a function break.
3. Somewhere convenient in your plugin, insert calls to Debug.breakFunc:
Debug.breakFunc (MyModule.myFunc)
If the function you want to break does not have a globally accessible name, you’ll need to do:
myFunc = Debug.breakFunc (myFunc)
To create a conditional breakpoint:
Debug.breakFunc (MyModule.myFunc, "i > n")
The conditional expression must be a string referring to global variables and argument names only.
Note that it doesn’t appear possible to resume from a breakpoint set in an LrView validation function (e.g. for an edit_field). Each time the debugger window grabs focus, the SDK generates a new call to the validation function, retriggering the breakpoint.
Every interaction in the debugger window is also logged in the Debug log (which defaults to plugin-directory/debug.log). The Log button opens the log file in your text editor. This is particularly useful if you want to see a result of Eval that is a very large table or string.
Debug looks for source files in the plugin's directory (_PLUGIN.path). If you use Require.path to load files from other directories, Debug will use the same search path to locate the sources.
If you have your own module-loading scheme, you can explicitly give Debug a search path:
Debug.path ("../common", "../base")
Note that files compiled by dofile appear to have absolute pathnames recorded in the compiled code, so if your module scheme uses dofile, it may not be necessary to use Debug.path.
See Debug.lua for complete documentation.
Debug.pp
Debug.pp is a pretty printer that formats any Lua value as a string suitable for logging or display. Debug.pp shows the contents of tables properly indented, showing each nested table at most once, even circular tables. The debugger uses Debug.pp for displaying values.
Debug.log
Debug.log is an LrLogger that writes to plugin-directory/debug.log. It only creates the file if the plugin actually outputs something. To log one or more values on a single line, converted to strings and separated by spaces:
Debug.logn ("n", n, "i > n", i > n)
To log a formatted string:
Debug.log:tracef ("%d items are stale", n)
To log one or more values pretty-printed with Debug.pp:
Debug.lognpp (table1, array1)
Debug.profileFunc
You can measure the total number of calls and elapsed time per call of a function using the Debug profiler:
myFunc = Debug.profileFunc (myFunc, "myFunc")
...execute code that calls myFunc one or more times...
Debug.logn ("\n", Debug.profileResults())
The profiler properly handles recursive calls to functions.